const { spawn } = require('child_process');
const dayjs = require('dayjs');
const { promises, createReadStream } = require('fs');
const { default: generator } = require('megalodon');
const { Readable } = require('stream');
const { Innertube } = require('youtubei.js');

const ACTIVATE_TIME = 60 * 60 * 1000;

const EPOCH = 1293829200000;
const TEMPLATE_REGEX = /{(\d+)}/g;

const REROLL_AMOUNT = 2;
const REROLL_EMOJI = '♻️';
const REROLL_MAX_TIME = 60 * 30 * 1000;

function normalize(str) {
  str = str.replaceAll('_', ' ');
  return str[0].toUpperCase() + str.slice(1);
}

module.exports = class App {
  constructor (config) {
    this.instance = generator(config.instance_engine, config.base_url, config.access_token);
    this.instance2 = generator(config.instance_engine, config.base_url.replace('http', 'ws'), config.access_token);

    this.socket = this.instance2.userSocket();

    Innertube.create().then(v => ( this.youtube = v ));

    this.processors = [];
    this.import();

    this.activate = this.activate.bind(this);
    this.onNotification = this.onNotification.bind(this);

    this.interval = setInterval(this.activate, ACTIVATE_TIME);
    this.socket.on('notification', this.onNotification);
  }

  async onNotification(notification) {
    if (notification.type !== 'emoji_reaction') return;

    const createdAt = new Date(notification.status.created_at);
    if (Date.now() - createdAt.getTime() >= REROLL_MAX_TIME || notification.status.media_attachments.length === 0) return;

    const reaction = notification.status.emoji_reactions.filter(x => x.name.trim() === REROLL_EMOJI)[0];
    if (!reaction) return;

    if (reaction.count >= REROLL_AMOUNT) {
      console.log('reroll');
      await this.instance.deleteStatus(notification.status.id);
      return await this.activate();
    }
  }

  async import() {
    const scan = await promises.readdir('searches/');

    for (const item of scan)
      this.processors.push(require('./searches/' + item));
  }

  getRandomProcessorOutput() {
    const { filters, query } = this.processors[Math.floor(Math.random() * this.processors.length)];
    const forms = [];

    for (const form of query.forms)
      switch (form[0]) {
        case 'number': {
          const number = Math.floor(Math.random() * (10 ** form[1] - 2)) + 1;
          forms.push(String(number).padStart(form[1]));
        } break;
        case 'date': {
          const timestamp = EPOCH + Math.floor(Math.random() * (Date.now() - EPOCH));
          forms.push(dayjs(timestamp).format(form[1]));
        } break;
        default: {
          console.warn('unknown form type:', form[0]);
          forms.push('');
        }
      }

    const queryTemplated = query.template.replace(TEMPLATE_REGEX, (_match, number) =>
      forms[number] || ''
    );

    return { query: queryTemplated, filters };
  }

  async activate(bailout = false) {
    const { query, filters } = this.getRandomProcessorOutput();

    Object.assign(filters, { type: 'video', sort_by: 'upload_date' });

    try {
      const results = await this.youtube.search('"' + query + '"', filters);
      if (!results.videos || results.videos.length === 0)
        throw new Error('no videos');

      const video = results.videos[Math.floor(Math.random() * results.videos.length)];
      const stream = Readable.from(
        await this.youtube.download(video.id, { format: 'any', quality: 'best' })
      );

      const randomFilename = 'tmp_' + Math.random().toString(16).slice(2) + '.mp4';

      const ffmpeg = spawn('ffmpeg', [
        '-i', '-',
        '-t', '00:00:30',
        '-c:v', 'libx264',
        '-crf', '23',
        '-preset', 'fast',
        '-c:a', 'aac',
        '-b:a', '128k',
        '-f', 'ismv',
        randomFilename
      ]);
      stream.pipe(ffmpeg.stdin);

      ffmpeg.stderr.on('data', (x) => console.log(x.toString()));

      // fuck off
      ffmpeg.stdin.on('error', () => {});
      ffmpeg.stdout.on('error', () => {});

      await new Promise(res =>
        ffmpeg.on('close', res)
      );

      const { data } = await this.instance.uploadMedia(createReadStream(randomFilename), {
        description: video.title
      });

      await promises.unlink(randomFilename);

      const readableFilters = Object.entries(filters)
        .map(x => normalize(x[0]) + ': ' + normalize(x[1]))
        .join('\n');

      const statusText = `${video.title}
      https://youtu.be/${video.id}

      Query: ${query}
      ${readableFilters}`.replaceAll(/^(\t| )+/gm, '');

      return await this.instance.postStatus(statusText, {
        media_ids: [ data.id ],
        visibility: 'private'
      });
    } catch (err) {
      console.error(err);
      if (!bailout)
        return await this.activate(true);
      else
        return await this.instance.postStatus('no video for now, we bailed out!', { visibility: 'private' });
    }
  }
};